/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */

package com.obixlabs.commons.xml;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;

/**
 * <p>
 * Simple NamespaceContext implementation which 
 * uses a basic map to link prefixes to URIs. Instances
 * of this implementation provide the following default mappings:
 * </p>
 * 
 * <p>
 * <table border=1>
 * 	<tbody>
 * 		<tr>
 * 			<td>XMLConstants.DEFAULT_NS_PREFIX</td>
 * 			<td>&quot;&quot;</td>		
 * 		</tr>
 * 		<tr>
 * 			<td>XMLConstants.NULL_NS_URI</td>
 * 			<td>&quot;&quot;</td>			
 * 		</tr>
 * 		<tr>
 * 			<td>XMLConstants.XMLConstants.XML_NS_PREFIX</td>
 * 			<td>XMLConstants.XML_NS_URI</td>			
 * 		</tr>
 * 		<tr>
 * 			<td>XMLConstants.XMLNS_ATTRIBUTE</td>
 * 			<td>XMLConstants.XMLNS_ATTRIBUTE_NS_URI</td>			
 * 		</tr>
 * 		<tr>
 * 			<td>&quot;xsd&quot;</td>
 * 			<td>XMLConstants.W3C_XML_SCHEMA_NS_URI</td>			
 * 		</tr>
 * 		<tr>
 * 			<td>&quot;xsi&quot;</td>
 * 			<td>XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI</td>			
 * 		</tr> 
 * 	</tbody>
 * </table>
 * </p>
 */
public class ObixNamespaceContext implements NamespaceContext
{
	/**
	 * <p>
	 * Internal map which links prefixes to URIs.
	 * </p>
	 */
	private Map<String, String> prefixToUriMap;

	/**
	 * <p>
	 * Constructor.
	 * </p>
	 */
	public ObixNamespaceContext()
	{ 
		prefixToUriMap = new HashMap<String, String>();
				
		registerPrefix(XMLConstants.DEFAULT_NS_PREFIX,"");
		registerPrefix(XMLConstants.NULL_NS_URI,"");
		registerPrefix(XMLConstants.XML_NS_PREFIX, 
						XMLConstants.XML_NS_URI);
		registerPrefix(XMLConstants.XMLNS_ATTRIBUTE, 
						XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
		registerPrefix("xsd",XMLConstants.W3C_XML_SCHEMA_NS_URI);
		registerPrefix("xsi",XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);		
	}
	
	@Override
	public synchronized String getNamespaceURI(String prefix)
	{
		String result;		
		assertArgumentValue(prefix);
		
		String key = prefix.toLowerCase();
		result = prefixToUriMap.get(key);
		
		return result;
	}

	@Override
	public synchronized String getPrefix(String namespaceURI)
	{
		String result;		
		assertArgumentValue(namespaceURI);
		
		String value = namespaceURI.toLowerCase();		
		result = getKey(value);
		
		return result;
	}
	
	/**
	 * <p>
	 * Maps the given prefix to the specified URI. This is done 
	 * by adding an entry to the instance's 
	 * {@link #prefixToUriMap internal prefix to URI map}.
	 * </p>
	 * @param prefix	The prefix to be mapped to the given URI.
	 * @param namespaceURI	The URI to which the given prefix is 
	 * to be mapped.
	 */
	public synchronized void registerPrefix(String prefix, 
											String namespaceURI)
	{
		assertArgumentValue(prefix);
		assertArgumentValue(namespaceURI);
		
		prefixToUriMap.put(	prefix.toLowerCase(), 
							namespaceURI.toLowerCase());
	}

	@Override
	public synchronized Iterator<String> getPrefixes(String namespaceURI)
	{
		Set<String> result = new HashSet<String>();
		assertArgumentValue(namespaceURI);
		
		Set<Entry<String, String>> entries = 
							prefixToUriMap.entrySet();
		
		String namespaceURIToLowerCase = 
					namespaceURI.toLowerCase();
		
		for (Entry<String, String> entry : entries)
		{
			if (entry.getValue().equals(namespaceURIToLowerCase))
				result.add(entry.getKey());
		}
		
		return result.iterator(); 
	}

	/**
	 * <p>
	 * Internal helper method which 
	 * returns the prefix of the given URI. This is achieved 
	 * by retrieving the key in the {@link #prefixToUriMap
	 * internal prefix to URI map} against which the given value 
	 * is stored.
	 * </p>
	 * 
	 * @param value	The value/URI whose key is to be retrieved 
	 * from the {@link #prefixToUriMap prefix to URI map}.
	 * 
	 * @return	The prefix (key) for the give value as taken 
	 * from the {@link #prefixToUriMap internal prefix to URI map}.
	 */
	private String getKey(String value)
	{
		String result = null;
		
		Set<Entry<String, String>> entries = 
							prefixToUriMap.entrySet();
		
		for (Entry<String, String> entry : entries)
		{
			if (entry.getValue().equals(value))
			{
				result = entry.getKey();
				break;
			}
		}
		
		return result;
	}
	
	/**
	 * <p>
	 * This class does not permit null values to be specified
	 * for either prefixes or URIs. This method asserts that 
	 * values specified for either of these parameter-types 
	 * is not null.
	 * </p>
	 * 
	 * @param arg	The parameter value to validate.
	 * @throws IllegalArgumentException If the specified parameter is null.
	 */
	private void assertArgumentValue(String arg)
	{
		if (arg==null)
			throw new IllegalArgumentException("Null parameter values are not allowed!"); 
	}
}